#include "..\include\CTerrain.h"

const DWORD CTerrain::TerrainVertex::FVF = D3DFVF_XYZ | D3DFVF_TEX1;

CTerrain::CTerrain(IDirect3DDevice9* pDevice, char* szFileName, int nNumVertsPerRow,
				   int nNumVertsPerColumn, int nCellSpacing, float fHeightScale) : 
									m_pDevice(pDevice),
									m_nNumVertsPerRow(nNumVertsPerRow),
									m_nNumVertsPerColumn(nNumVertsPerColumn),
									m_nCellSpacing(nCellSpacing),
									m_fHeightScale(fHeightScale)
{
	m_nNumCellsPerRow 			= m_nNumVertsPerRow -1;
	m_nNumCellsPerColumn 	= m_nNumVertsPerColumn -1;
	m_nWidth 								= m_nNumCellsPerRow * m_nCellSpacing;
	m_nDepth								= m_nNumCellsPerColumn * m_nCellSpacing;
	m_nNumVerticies 				= m_nNumVertsPerRow * m_nNumVertsPerColumn;
	m_nNumTriangles 				= (m_nNumCellsPerRow * m_nNumCellsPerColumn) * 2;
	m_bLava = false;

	// Read the raw image file and store the bytes in our array.
	if(!ReadRawFile(szFileName))
	{
		::MessageBox(0, "ReadRawFile() - Failed", 0, 0);
		::PostQuitMessage(0);   
	}

	// Scale the heightmap
	for(unsigned int i = 0; i < m_vHeightMap.size(); i++)
		m_vHeightMap[i] *= m_fHeightScale;

     // compute the vertices   
     if( !ComputeVerticies() )   
     {   
         ::MessageBox(0, "ComputeVertices - Failed", 0, 0);   
         ::PostQuitMessage(0);   
     }   
    
     // compute the indices   
     if( !ComputeIndicies() )   
     {   
         ::MessageBox(0, "ComputeIndices - Failed", 0, 0);   
         ::PostQuitMessage(0);   
     }   
}

CTerrain::~CTerrain()
{
     //d3d::Release<idirect3dvertexbuffer9*>(m_vb);   
     //d3d::Release<idirect3dindexbuffer9*>(m_ib);   
     //d3d::Release<idirect3dtexture9*>(m_pTex);
}

bool CTerrain::ReadRawFile(char* szFileName)
{
	// A height for each vertex
	std::vector<BYTE> in(m_nNumVerticies);

	std::ifstream inFile(szFileName, std::ios_base::binary);

	if(inFile == 0)
		return false;

	inFile.read((char*)&in[0], in.size());
	inFile.close();

	// copy BYTE vector to int vector
	m_vHeightMap.resize(m_nNumVerticies);
	for(unsigned int i = 0; i < in.size(); i++)
		m_vHeightMap[i] = in[i];

	return true;
}

bool CTerrain::ComputeVerticies(void)
{
	HRESULT hr = 0;

	hr = m_pDevice->CreateVertexBuffer(m_nNumVerticies * sizeof(TerrainVertex),
		D3DUSAGE_WRITEONLY,
		TerrainVertex::FVF,
		D3DPOOL_MANAGED,
		&m_vb,
		0);

	if(FAILED(hr))
		return false;

	// Coords to start generating verticies at
	int nStartX = -m_nWidth / 2;
	int nStartZ = m_nDepth / 2;

	// Coords to end generating verticies at
	int nEndX = m_nWidth / 2;
	int nEndZ = -m_nDepth / 2;

	// Compute the increment size of the texture coords from one vert to the next.
	float uCoordIncrementSize = 1.0f / (float)m_nNumCellsPerRow;
	float vCoordIncrementSize = 1.0f / (float)m_nNumCellsPerColumn;

	TerrainVertex* v = 0;
	m_vb->Lock(0, 0, (void**)&v, 0);

	int i = 0;
	for(int z = nStartZ; z >= nEndZ; z -= m_nCellSpacing)
	{
		int j = 0;
		for(int x = nStartX; x <= nEndX; x += m_nCellSpacing)
		{
			int index = i * m_nNumVertsPerRow + j;

			v[index] = TerrainVertex((float)x,
				(float)m_vHeightMap[index],
				(float)z,
				(float)j * uCoordIncrementSize,
				(float)i * vCoordIncrementSize);
			j++; // next column
		}
		i++;  //  next row
	}

	m_vb->Unlock();

	return true;
}

bool CTerrain::ComputeIndicies(void)
{
	HRESULT hr = 0;   

	hr = m_pDevice->CreateIndexBuffer(   
		m_nNumTriangles * 3 * sizeof(WORD), // 3 indices per triangle   
		D3DUSAGE_WRITEONLY,   
		D3DFMT_INDEX16,   
		D3DPOOL_MANAGED,   
		&m_ib,   
		0);   

	if(FAILED(hr))   
		return false;   

	WORD* indices = 0;   
	m_ib->Lock(0, 0, (void**)&indices, 0);   

	// index to start of a group of 6 indices that describe the   
	// two triangles that make up a quad   
	int baseIndex = 0;   

	// loop through and compute the triangles of each quad   
	for(int i = 0; i < m_nNumCellsPerColumn; i++)   
	{   
		for(int j = 0; j < m_nNumCellsPerRow; j++)   
		{   
			indices[baseIndex]     =   i   * m_nNumVertsPerRow + j;   
			indices[baseIndex + 1] =   i   * m_nNumVertsPerRow + j + 1;   
			indices[baseIndex + 2] = (i+1) * m_nNumVertsPerRow + j;   

			indices[baseIndex + 3] = (i+1) * m_nNumVertsPerRow + j;   
			indices[baseIndex + 4] =   i   * m_nNumVertsPerRow + j + 1;   
			indices[baseIndex + 5] = (i+1) * m_nNumVertsPerRow + j + 1;   

			// next quad   
			baseIndex += 6;   
		}   
	}   

	m_ib->Unlock();   
	return true;
}

int CTerrain::GetHeightMapEntry(int nRow, int nColumn) const
{
	return m_vHeightMap[nRow * m_nNumVertsPerRow + nColumn];
}

void CTerrain::SetHeightMapEntry(int nRow, int nColumn, int nValue)
{
	m_vHeightMap[nRow * m_nNumVertsPerRow + nColumn] = nValue;
}

bool CTerrain::LoadTexture(char* szFileName)   
{   
	HRESULT hr = 0;   

	hr = D3DXCreateTextureFromFile(   
		m_pDevice,   
		szFileName,   
		&m_pTex);   

	if(FAILED(hr))   
		return false;   

	return true;   
}   

bool CTerrain::Draw(D3DXMATRIX* world, bool drawTris)
{
	HRESULT hr = 0;   

	if( m_pDevice )   
	{   
		m_pDevice->SetTransform(D3DTS_WORLD, world);   

		m_pDevice->SetStreamSource(0, m_vb, 0, sizeof(TerrainVertex));   
		m_pDevice->SetFVF(TerrainVertex::FVF);   
		m_pDevice->SetIndices(m_ib);   

		m_pDevice->SetTexture(0, m_pTex);   

		// turn off lighting since we're lighting it ourselves   
		m_pDevice->SetRenderState(D3DRS_LIGHTING, false);   

		hr =m_pDevice->DrawIndexedPrimitive(   
			D3DPT_TRIANGLELIST,   
			0,   
			0,   
			m_nNumVerticies,   
			0,   
			m_nNumTriangles);   

		m_pDevice->SetRenderState(D3DRS_LIGHTING, true);   

		if( drawTris )   
		{   
			m_pDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);   
			hr = m_pDevice->DrawIndexedPrimitive(   
				D3DPT_TRIANGLELIST,   
				0,   
				0,   
				m_nNumVerticies,   
				0,   
				m_nNumTriangles);   

			m_pDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);   
		}   

		if(FAILED(hr))   
			return false;   
	}   

	return true; 
}

bool CTerrain::GenTexture(D3DXVECTOR3* directionToLight)   
{   
      // Method fills the top surface of a texture procedurally.  Then   
      // lights the top surface.  Finally, it fills the other mipmap   
      // surfaces based on the top surface data using D3DXFilterTexture.   
     
      HRESULT hr = 0;   
     
      // texel for each quad cell   
      int texWidth  = m_nNumCellsPerRow;   
      int texHeight = m_nNumCellsPerColumn;   
     
      // create an empty texture   
      hr = D3DXCreateTexture(   
          m_pDevice,   
          texWidth, texHeight,   
          0, // create a complete mipmap chain   
          0, // usage   
          D3DFMT_X8R8G8B8,// 32 bit XRGB format   
          D3DPOOL_MANAGED, &m_pTex);   
     
      if(FAILED(hr))   
          return false;   
     
      D3DSURFACE_DESC textureDesc;    
      m_pTex->GetLevelDesc(0 /*level*/, &textureDesc);   
     
      // make sure we got the requested format because our code    
      // that fills the texture is hard coded to a 32 bit pixel depth.   
      if( textureDesc.Format != D3DFMT_X8R8G8B8 )   
          return false;   
             
      D3DLOCKED_RECT lockedRect;   
      m_pTex->LockRect(0/*lock top surface*/, &lockedRect,    
          0 /* lock entire tex*/, 0/*flags*/);            
     
      DWORD* imageData = (DWORD*)lockedRect.pBits;   
      for(int i = 0; i < texHeight; i++)   
      {   
          for(int j = 0; j < texWidth; j++)   
          {   
              D3DXCOLOR c;   
     
              // get height of upper left vertex of quad.   
              float height = (float)GetHeightMapEntry(i, j) / m_fHeightScale;   
     

			  if(m_bLava)
			  {
				  if( (height) < 42.5f )            c = D3DCOLOR_XRGB(200, 0, 50);//d3d::BEACH_SAND; 
				  else if( (height) < 56.0f)    c = D3DCOLOR_XRGB(255, 155, 17);
				  else if( (height) < 85.0f )   c = D3DCOLOR_XRGB(0, 0, 0);//d3d::LIGHT_YELLOW_GREEN;   
				  else if( (height) < 127.5f ) c = D3DCOLOR_XRGB(  100, 100,  100);//d3d::PUREGREEN;   
				  else if( (height) < 170.0f ) c = D3DCOLOR_XRGB( 155, 155,  155);//d3d::DARK_YELLOW_GREEN;   
				  else if( (height) < 212.5f ) c = D3DCOLOR_XRGB(200, 200,  200);//d3d::DARKBROWN;   
				  else                                   c = D3DCOLOR_XRGB(255, 255, 255);//d3d::WHITE;  
			  }
			  else
			  {
				  if( (height) < 42.5f )            c = D3DCOLOR_XRGB(255, 249, 190);//d3d::BEACH_SAND; 
				  else if( (height) < 56.0f)    c = D3DCOLOR_XRGB(200, 200, 157);
				  else if( (height) < 85.0f )   c = D3DCOLOR_XRGB(124, 197, 118);//d3d::LIGHT_YELLOW_GREEN;   
				  else if( (height) < 127.5f ) c = D3DCOLOR_XRGB(  0, 166,  81);//d3d::PUREGREEN;   
				  else if( (height) < 170.0f ) c = D3DCOLOR_XRGB( 25, 123,  48);//d3d::DARK_YELLOW_GREEN;   
				  else if( (height) < 212.5f ) c = D3DCOLOR_XRGB(115, 100,  87);//d3d::DARKBROWN;   
				  else                                   c = D3DCOLOR_XRGB(255, 255, 255);//d3d::WHITE;  
			  }

              // fill locked data, note we divide the pitch by four because the   
              // pitch is given in bytes and there are 4 bytes per DWORD.   
              imageData[i * lockedRect.Pitch / 4 + j] = (D3DCOLOR)c;   
          }   
      }   
     
      m_pTex->UnlockRect(0);   
     
      if(!LightTerrain(directionToLight))   
      {   
          ::MessageBox(0, "lightTerrain() - FAILED", 0, 0);   
          return false;   
      }   
         
      hr = D3DXFilterTexture(   
          m_pTex,   
          0, // default palette   
          0, // use top level as source level   
          D3DX_DEFAULT); // default filter   
     
      if(FAILED(hr))   
      {   
          ::MessageBox(0, "D3DXFilterTexture() - FAILED", 0, 0);   
          return false;   
      }   
     
      return true;   
  }   
     
  bool CTerrain::LightTerrain(D3DXVECTOR3* directionToLight)   
  {   
      HRESULT hr = 0;   
     
      D3DSURFACE_DESC textureDesc;    
      m_pTex->GetLevelDesc(0 /*level*/, &textureDesc);   
     
      // make sure we got the requested format because our code that fills the   
      // texture is hard coded to a 32 bit pixel depth.   
      if( textureDesc.Format != D3DFMT_X8R8G8B8 )   
          return false;   
             
      D3DLOCKED_RECT lockedRect;   
      m_pTex->LockRect(   
          0,          // lock top surface level in mipmap chain   
          &lockedRect,// pointer to receive locked data   
          0,          // lock entire texture image   
          0);         // no lock flags specified   
     
      DWORD* imageData = (DWORD*)lockedRect.pBits;   
      for(int i = 0; i < textureDesc.Height; i++)   
      {   
          for(int j = 0; j < textureDesc.Width; j++)   
          {   
              // index into texture, note we use the pitch and divide by    
              // four since the pitch is given in bytes and there are    
              // 4 bytes per DWORD.   
              int index = i * lockedRect.Pitch / 4 + j;   
     
              // get current color of quad   
              D3DXCOLOR c( imageData[index] );   
     
              // shade current quad   
              c *= ComputeShade(i, j, directionToLight);;   
     
              // save shaded color   
              imageData[index] = (D3DCOLOR)c;   
          }   
      }   
     
      m_pTex->UnlockRect(0);   
     
      return true;   
  }   
     
  float CTerrain::ComputeShade(int cellRow, int cellCol, D3DXVECTOR3* directionToLight)   
  {   
      // get heights of three vertices on the quad   
      float heightA = GetHeightMapEntry(cellRow,   cellCol);   
      float heightB = GetHeightMapEntry(cellRow,   cellCol+1);   
      float heightC = GetHeightMapEntry(cellRow+1, cellCol);   
     
      // build two vectors on the quad   
      D3DXVECTOR3 u(m_nCellSpacing, heightB - heightA, 0.0f);   
      D3DXVECTOR3 v(0.0f,         heightC - heightA, -m_nCellSpacing);   
     
      // find the normal by taking the cross product of two   
      // vectors on the quad.   
      D3DXVECTOR3 n;   
      D3DXVec3Cross(&n, &u, &v);   
      D3DXVec3Normalize(&n, &n);   
     
      float cosine = D3DXVec3Dot(&n, directionToLight);   
     
      if(cosine < 0.0f)   
          cosine = 0.0f;   
     
      return cosine;   
  }   
     
  float CTerrain::GetHeight(float x, float z)   
  {   
      // Translate on xz-plane by the transformation that takes   
      // the terrain START point to the origin.   
      x = ((float)m_nWidth / 2.0f) + x;   
      z = ((float)m_nDepth / 2.0f) - z;   
     
      // Scale down by the transformation that makes the    
      // cellspacing equal to one.  This is given by    
      // 1 / cellspacing since; cellspacing * 1 / cellspacing = 1.   
      x /= (float)m_nCellSpacing;   
      z /= (float)m_nCellSpacing;   
     
      // From now on, we will interpret our positive z-axis as   
      // going in the 'down' direction, rather than the 'up' direction.   
      // This allows to extract the row and column simply by 'flooring'   
      // x and z:   
     
      float col = ::floorf(x);   
      float row = ::floorf(z);   
     
      // get the heights of the quad we're in:   
      //    
      //  A   B   
      //  *---*   
      //  | / |   
      //  *---*     
      //  C   D   

	  // Do a little error checking
	  if(row < 0 || row > this->m_nNumCellsPerRow - 1 ||
		  col < 0 || col > this->m_nNumCellsPerColumn - 1)
		  return 0;

      float A = GetHeightMapEntry(row,   col);   
      float B = GetHeightMapEntry(row,   col+1);   
      float C = GetHeightMapEntry(row+1, col);   
      float D = GetHeightMapEntry(row+1, col+1);   
     
      //   
      // Find the triangle we are in:   
      //   
     
      // Translate by the transformation that takes the upper-left   
      // corner of the cell we are in to the origin.  Recall that our    
      // cellspacing was nomalized to 1.  Thus we have a unit square   
      // at the origin of our +x -> 'right' and +z -> 'down' system.   
      float dx = x - col;   
      float dz = z - row;   
     
      // Note the below compuations of u and v are unneccessary, we really   
      // only need the height, but we compute the entire vector to emphasis   
      // the books discussion.   
      float height = 0.0f;   
      if(dz < 1.0f - dx)  // upper triangle ABC   
      {   
          float uy = B - A; // A->B   
          float vy = C - A; // A->C   
     
          // Linearly interpolate on each vector.  The height is the vertex   
          // height the vectors u and v originate from {A}, plus the heights   
          // found by interpolating on each vector u and v.   
          height = A + Lerp(0.0f, uy, dx) + Lerp(0.0f, vy, dz);   
      }   
      else // lower triangle DCB   
      {   
          float uy = C - D; // D->C   
          float vy = B - D; // D->B   
     
          // Linearly interpolate on each vector.  The height is the vertex   
          // height the vectors u and v originate from {D}, plus the heights   
          // found by interpolating on each vector u and v.   
          height = D + Lerp(0.0f, uy, 1.0f - dx) + Lerp(0.0f, vy, 1.0f - dz);   
      }   
     
      return height;   
  }   

  float CTerrain::Lerp(float a, float b, float t)   
 {   
     return a - (a*t) + (b*t);   
 }   
